package GUI;

import edu.uci.ics.jung.exceptions.ConstraintViolationException;
import edu.uci.ics.jung.graph.Edge;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.Vertex;
import edu.uci.ics.jung.graph.decorators.AbstractEdgePaintFunction;
import edu.uci.ics.jung.graph.decorators.EdgeStrokeFunction;
import edu.uci.ics.jung.graph.decorators.NumberEdgeValue;
import edu.uci.ics.jung.graph.decorators.StringLabeller;
import edu.uci.ics.jung.graph.decorators.VertexPaintFunction;
import edu.uci.ics.jung.graph.impl.UndirectedSparseEdge;
import edu.uci.ics.jung.graph.impl.UndirectedSparseGraph;
import edu.uci.ics.jung.graph.impl.UndirectedSparseVertex;
import edu.uci.ics.jung.utils.PredicateUtils;
import edu.uci.ics.jung.utils.UserData;
import edu.uci.ics.jung.visualization.*;
import edu.uci.ics.jung.visualization.contrib.CircleLayout;
import edu.uci.ics.jung.visualization.contrib.KKLayout;
import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.PluggableGraphMouse;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import org.apache.commons.collections.Predicate;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;

/**
 * Wizualizator do grafów.
 * @author Piotrek
 */
class GraphVisualizer {

    private static final String COLOR_KEY = "color";
    private static final String LABEL_KEY = "label";
    protected static Graph jungGraph;
    protected static VisualizationViewer graphVisualizationViewer;
    private static StringLabeller vertexLabeller;
    protected static int graph_index;

    private static final class LayoutChooser implements ActionListener {

        private final JComboBox jcb;
        private final VisualizationViewer vv;

        private LayoutChooser(JComboBox jcb, VisualizationViewer vv) {
            super();
            this.jcb = jcb;
            this.vv = vv;
        }

        public void actionPerformed(ActionEvent arg0) {
            Object[] constructorArgs =
                    {jungGraph};

            Class layoutC = (Class) jcb.getSelectedItem();
            Class lay = layoutC;
            try {
                Constructor constructor = lay.getConstructor(new Class[]{Graph.class});
                Object o = constructor.newInstance(constructorArgs);
                Layout l = (Layout) o;
                vv.stop();
                vv.setGraphLayout(l, false);
                vv.restart();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Tworzy panel do wyświetlania grafów
     * @param nodes wierzchołki
     * @param colorPalette paleta kolorów
     * @return panel z grafem
     */
    public static JPanel getGraphPanel(List nodes, List colorPalette) {

        jungGraph = createJungGraph(nodes, colorPalette);

        final VisualizationViewer vv = getGraphVisualization(colorPalette);

        final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse();
        vv.setGraphMouse(graphMouse);

        final JPanel jp = new JPanel();

        // skalowanie
        final ScalingControl scaler = new CrossoverScalingControl();

        // zomm +
        JButton plus = new JButton("+");
        plus.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                scaler.scale(vv, 1.1f, vv.getCenter());
            }
        });
        // zoom -
        JButton minus = new JButton("-");
        minus.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                scaler.scale(vv, 1 / 1.1f, vv.getCenter());
            }
        });

        // menu       
        JMenu menu = new JMenu("Opcje");
        // zapis do pliku
        menu.add(new AbstractAction("Zapisz jako..") {

            public void actionPerformed(ActionEvent e) {
                JFileChooser chooser = new JFileChooser();
                // zapisz jako menu-filtr
                chooser.addChoosableFileFilter(new JPGFilter());
                chooser.addChoosableFileFilter(new PNFFilter());
                chooser.addChoosableFileFilter(new GMLFilter());
                chooser.setAcceptAllFileFilterUsed(false);
                int option = chooser.showSaveDialog(jp);
                if (option == JFileChooser.APPROVE_OPTION) {
                    File file = chooser.getSelectedFile();
                    // wybór rozszerzenia pliku
                    // jpg
                    if (chooser.getFileFilter().toString().equalsIgnoreCase(Utils.jpg)) {
                        GraphIO.writeJPEGImage(file, vv);
                    // pnf
                    } else if (chooser.getFileFilter().toString().equalsIgnoreCase(Utils.pnf)) {
                        GraphIO.writeGraphPNF(jungGraph, file.getPath());
                    // gml
                    } else if (chooser.getFileFilter().toString().equalsIgnoreCase(Utils.gml)) {
                        GraphIO.writeGraphGML(jungGraph, file.getPath());
                    }
                }
            }
        });
        // wydruk
        menu.add(new AbstractAction("Drukuj") {

            public void actionPerformed(ActionEvent e) {
                GraphIO.print(vv);
            }
        });

        JMenuBar menuBar = new JMenuBar();
        menuBar.add(menu);

        JComboBox modeBox = graphMouse.getModeComboBox();
        modeBox.addItemListener(((DefaultModalGraphMouse) vv.getGraphMouse()).getModeListener());

        vv.setPickSupport(new ShapePickSupport());

        jp.setBackground(Color.WHITE);
        jp.setLayout(new BorderLayout());
        jp.add(vv, BorderLayout.CENTER);

        Class[] combos = getCombos();
        final JComboBox jcb = new JComboBox(combos);
        // renderer
        jcb.setRenderer(new DefaultListCellRenderer() {

            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                String valueString = value.toString();
                valueString = valueString.substring(valueString.lastIndexOf('.') + 1);
                return super.getListCellRendererComponent(list, valueString, index, isSelected,
                        cellHasFocus);
            }
        });
        jcb.addActionListener(new LayoutChooser(jcb, vv));
        jcb.setSelectedItem(FRLayout.class);

        JPanel control_panel = new JPanel(new GridLayout(1, 2));
        JPanel topControls = new JPanel();
        control_panel.add(topControls);
        jp.add(control_panel, BorderLayout.NORTH);

        topControls.add(jcb);
        topControls.add(menuBar);
        topControls.add(plus);
        topControls.add(minus);
        topControls.add(modeBox);
        return jp;
    }

    /**
     * Wybór layout
     * @return dostępne layout
     */
    private static Class[] getCombos() {
        List layouts = new ArrayList();
        layouts.add(KKLayout.class);
        layouts.add(FRLayout.class);
        layouts.add(CircleLayout.class);
        layouts.add(SpringLayout.class);
        layouts.add(ISOMLayout.class);
        return (Class[]) layouts.toArray(new Class[0]);
    }

    /**
     * Tworzy wizualizację dla grafu
     * @param colorPalette paleta kolorów
     * @return wizualizator do grafu
     */
    private static VisualizationViewer getGraphVisualization(List colorPalette) {

        // właściwości do renderowania:
        final PluggableRenderer pluggableRenderer = new PluggableRenderer();
        pluggableRenderer.setVertexPaintFunction(new CustomVertexPaintFunction());
        pluggableRenderer.setEdgePaintFunction(new CustomEdgePaintFunction());
        EdgeWeightStrokeFunction ewcs = new EdgeWeightStrokeFunction();
        pluggableRenderer.setEdgeStrokeFunction(ewcs);
        pluggableRenderer.setVertexStringer(vertexLabeller);

        FRLayout layout = new FRLayout(jungGraph);
        graphVisualizationViewer = new VisualizationViewer(layout,
                pluggableRenderer);

        graphVisualizationViewer.setPickSupport(new ShapePickSupport());

        //support dla myszy
        PluggableGraphMouse gm = new PluggableGraphMouse();
        graphVisualizationViewer.setGraphMouse(gm);
        gm.add(new GraphPanelPopupMouse());   // menu

        return graphVisualizationViewer;
    }

    /**
     * Zmienia layout
     * @param lay layout do zamiany
     */
    public void ChangeGraphLayout(Class lay) {
        try {
            Constructor constructor = lay.getConstructor(new Class[]{Graph.class});
            Object o = constructor.newInstance(new Object[]{jungGraph});
            Layout l = (Layout) o;
            graphVisualizationViewer.stop();
            graphVisualizationViewer.setGraphLayout(l);
            graphVisualizationViewer.restart();
            graphVisualizationViewer.stop(); // put in to stop the layout algo from
        // iteratively updating the visualization
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // tworzy wierzchoki i krawędzie
    private static Graph createJungGraph(List vertexInfoList, List palette) {
        final int vertexCount = vertexInfoList.size();
        Graph graph = new UndirectedSparseGraph();
        List vertexList = new ArrayList(vertexCount);

        // najpierw dodajemy wierzchołki
        // później krawędzie
        for (int i = 0; i < vertexCount; i++) {
            vertexList.add(new UndirectedSparseVertex());
            graph.addVertex((Vertex) vertexList.get(i));
        }

        // labeler dla wierzołków
        vertexLabeller = StringLabeller.getLabeller(graph, LABEL_KEY);

        for (int i = 0; i < vertexCount; ++i) {
            VertexInfo vertexInfo = (VertexInfo) vertexInfoList.get(i);
            vertexInfo.color = -1;
            UndirectedSparseVertex vertex = (UndirectedSparseVertex) vertexList.get(i);

            // informacja o kolorach
            vertex.addUserDatum(COLOR_KEY, getColor(palette, vertexInfo.color), UserData.CLONE);

            try {
                // label do wierzchołka
                vertexLabeller.setLabel(vertex, vertexInfo.label);
            } catch (StringLabeller.UniqueLabelException e) {
                e.printStackTrace();
            }
            // dodanie krawędzi
            for (int j = 0; j < vertexInfo.edges.length; j++) {
                int connectedNodeValue = vertexInfo.edges[j];
                if (connectedNodeValue != 0) {
                    UndirectedSparseEdge edge = new UndirectedSparseEdge(vertex, (UndirectedSparseVertex) vertexList.get(j));
                    edge.addUserDatum(COLOR_KEY, getColor(palette, connectedNodeValue), UserData.CLONE);
                    try {
                        graph.addEdge(edge);
                    } catch (ConstraintViolationException e) {
                        e.printStackTrace();
                        final Predicate predicate = e.getViolatedConstraint();
                        PredicateUtils.evaluateNestedPredicates(predicate, edge);
                    }
                }
            }
        }

        return graph;
    }

    // wybór koloru z palety
    private static Color getColor(List palette, int colorIndex) {
        Color defaultColor = Color.black;
        if (colorIndex < 0 || colorIndex >= palette.size()) {
            return defaultColor;
        }
        Color color = (Color) palette.get(colorIndex);
        if (color == null) {
            return defaultColor;
        }

        return color;
    }

    // rysowanie krawędzi o podanym kolorze.
    public static class CustomEdgePaintFunction extends AbstractEdgePaintFunction {

        public Paint getDrawPaint(Edge e) {
            return (Color) e.getUserDatum(COLOR_KEY);
        }
    }

    private final static class EdgeWeightStrokeFunction
            implements EdgeStrokeFunction {

        protected static final Stroke basic = new BasicStroke(1);
        protected static final Stroke heavy = new BasicStroke(4);
        protected static final Stroke dotted = PluggableRenderer.DOTTED;
        protected boolean weighted = false;
        protected NumberEdgeValue edge_weight;

        public void setWeighted(boolean weighted) {
            this.weighted = weighted;
        }

        public Stroke getStroke(Edge e) {
            return heavy;
        }

        protected boolean drawHeavy(Edge e) {
            double value = edge_weight.getNumber(e).doubleValue();
            if (value > 0.7) {
                return true;
            } else {
                return false;
            }
        }
    }


    // wypełnianie wierzchołka kolorem
    public static class CustomVertexPaintFunction implements VertexPaintFunction {

        public Paint getFillPaint(Vertex vertex) {
            return (Color) vertex.getUserDatum(COLOR_KEY);
        }

        public Paint getDrawPaint(Vertex vertex) {
            return (Color) vertex.getUserDatum(COLOR_KEY);
        }
    }

    public static VisualizationViewer getGraphVisualizationViewer() {
        return graphVisualizationViewer;
    }
}

